import axios from 'axios'
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, Canceler, InternalAxiosRequestConfig } from 'axios'
import { showTip } from '.';
import router from '@/router';
/**请求列表 */
let pendingRequest = new Map<string, Canceler>();
/** 传入axios的config 函数返回唯一的请求key **/
function getRequestKey(config: InternalAxiosRequestConfig) {
    let { method, url, params, data } = config;
    //请求相同，参数不同的也算作相同请求。 
    return [method, url?.split('?')[0]].join('&') //去掉了请求参数
    // 参数相同时阻止重复请求：
    return [method, url, JSON.stringify(params), JSON.stringify(data)].join("&");
};

/**添加请求信息到Map中 */
function addPendingRequest(config: InternalAxiosRequestConfig) {
    // console.log(config.url)
    let requestKey = getRequestKey(config);
    config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if (!pendingRequest.has(requestKey)) {
            pendingRequest.set(requestKey, cancel);
        }
    });
}
/**取消重复请求 */
function removePendingRequest(config: InternalAxiosRequestConfig) {
    let requestKey = getRequestKey(config);
    if (pendingRequest.has(requestKey)) {
        // 如果是重复的请求，则执行对应的cancel函数
        let cancel = pendingRequest.get(requestKey);
        cancel?.(requestKey);
        // 将前一次重复的请求移除
        pendingRequest.delete(requestKey);
    }
}


// 导出myRequest，可以用来自定义传递配置来创建实例
export class myRequest {
    /**axios 实例 */
    instance: AxiosInstance
    //根路径，需要在不同的环境下用不同的根路径
    baseConfig: AxiosRequestConfig = { baseURL: import.meta.env.VITE_URL, timeout: 60000 }


    /**构造函数，在这里进行类的初始化，并配置拦截器等 */
    constructor(config: AxiosRequestConfig) {
        // 使用axios.create创建axios实例，配置为基础配置和我们传递进来的配置
        this.instance = axios.create(Object.assign(this.baseConfig, config))
        //请求拦截器：在这里添加token
        this.instance.interceptors.request.use(
            (config: InternalAxiosRequestConfig) => {
                removePendingRequest(config);// 检查是否存在重复请求，若存在则取消已发的请求 
                addPendingRequest(config);// 把当前请求信息添加到pendingRequest对象中
                const token = localStorage.getItem("token") as string
                if (token) {
                    config.headers!.Authorization = token;
                    config.headers.token = token
                }
                return config
            },
            (err: any) => {
                showTip('未知错误', "error")
                return Promise.reject(err)
            },
        );
        //响应拦截器
        this.instance.interceptors.response.use(
            (res: AxiosResponse<any>) => {
                removePendingRequest(res.config);//请求成功了就移除请求 
                if (res.data.code === 200) {
                    return res.data.data //直接返回装着数据的data
                } else {
                    if (res.data instanceof Blob) return res.data//如果返回的是blob，就放行 
                    let message = res.data.msg
                    switch (res.data.code) {
                        case 300://jwt错误，需要清除token，同时返回主页
                            localStorage.removeItem("token")
                            localStorage.removeItem("user")
                            router.push('/')
                            message = '登录态过期！'
                            break;
                        case 403://无权限   
                            router.push('/registration')
                            message = '无权限访问'
                            break;
                        case 502://业务错误

                            break;
                        default:
                            console.log('未知请求错误 ', res.data);
                            break;
                    }
                    showTip(`${message}`, "error")
                    return Promise.reject(message)//返回提示信息，可能可以在catch中用到
                }
            },
            (err: AxiosError) => {
                removePendingRequest(err.config || {} as InternalAxiosRequestConfig<any>);// 从pendingRequest对象中移除请求 
                console.log('请求拦截器：', err);
                //如果是被取消的请求
                if (err.code === 'ERR_CANCELED') {
                    console.log("被取消的重复请求：" + err.message);
                    return Promise.reject('请求重复，取消')
                }
                // 这里用来处理http常见错误，进行全局提示
                let message = err.message;
                // 这里错误消息可以使用全局弹框展示出来  
                showTip(`${message}，请检查网络或联系管理员！`, "error")
                // 这里是AxiosError类型，所以一般我们只reject我们需要的响应即可 
                return Promise.reject(err) //.response
            },
        );
    }

    /** 普通请求方法 
     * @param config 请求配置，遵循axios的配置 
     */
    public request(config: AxiosRequestConfig): Promise<AxiosResponse> {
        return this.instance.request(config)
    }
    /** get方法
     * @param url 请求路径 query可以写在里面
     * @param data 请求参数，可以把参数写为一个对象传递在这里，我会把对象转为query查询字符串
     * @param config 请求配置 
     * @T 返回值类型
     * @D 请求参数类型 
     */
    public get<T = any, D = Record<string, any>>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
        /**转换后的query参数 */
        let query = ""
        //进行对象转query
        if (data) {
            for (let i in data) { // 循环遍历对象
                let value = data[i] as string  // 定义变量接收对象的value值
                if (Array.isArray(value)) {// 若对象的value值为数组
                    value = value.join(",") //则进行join打断
                }
                query += `&${i}=${value}`// 进行字符串拼接
            }
            query = query.replace('&', '?')// 把第一个&替换为?
        }
        return new Promise(async (resolve, reject) => {
            try {
                const res = await this.instance.get<T>(url + query, config) as T //因为前面拦截器，这里得到的实际就是最后的数据
                resolve(res)
            } catch (error: any) {
                reject(error?.message || error?.data || error)
            }
        })
    }
    /**  post方法 
     * @param url 请求路径，如果有query参数请写在这
     * @param data body请求参数
     * @param config post请求配置 
     * @T 返回值类型
     * @D 请求参数类型 
     */
    public post<T = any, D = Record<string, any>>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
        return new Promise(async (resolve, reject) => {
            try {
                const res = await this.instance.post<T>(url, data, config) as T //因为前面拦截器，这里得到的实际就是最后的数据
                resolve(res)
            } catch (error: any) {
                reject(error?.message || error?.data || error)
            }
        })
    }
    /** put方法
     * @param url 请求路径
     * @param data 请求参数
     * @param config  请求配置
     * @T 返回值类型
     * @D 请求参数类型 
     */
    public put<T = any, D = Record<string, any>>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
        return new Promise(async (resolve, reject) => {
            try {
                const res = await this.instance.put<T>(url, data, config) as T //因为前面拦截器，这里得到的实际就是最后的数据
                resolve(res)
            } catch (error: any) {
                reject(error?.message || error?.data || error)
            }
        })
    }
    /** delete方法 
     * @param url 请求路径
     * @param config 请求参数 
     * @T 返回值类型
     * @D 请求参数类型 
     */
    public delete<T = any, D = Record<string, any>>(url: string, config?: AxiosRequestConfig): Promise<T> {
        return new Promise(async (resolve, reject) => {
            try {
                const res = await this.instance.delete<T>(url, config) as T //因为前面拦截器，这里得到的实际就是最后的数据
                resolve(res)
            } catch (error: any) {
                reject(error?.message || error?.data || error)
            }
        })
    }
    /**请求blob数据并导出下载 
     * @param url url
     * @param data post参数
     * @param name 导出的文件名，不带后缀，后缀为xlsx表格 
     */
    public exportFile<T extends Record<string, any>>(url: string, data: T, name: string): Promise<void> {
        return new Promise(async (resolve, reject) => {
            try {
                showTip("正在导出，请稍后", "info")
                const res = await request.post<Blob, T>(url, data, {
                    responseType: "blob"
                })
                const blob = new Blob([res]);
                const fileName = name + '.xlsx';
                const elink = document.createElement('a');
                elink.download = fileName;
                elink.style.display = 'none';
                elink.href = URL.createObjectURL(blob);
                document.body.appendChild(elink);
                elink.click();
                URL.revokeObjectURL(elink.href); // 释放URL 对象
                document.body.removeChild(elink);
                showTip("导出成功！")
                resolve()
            } catch (error: any) {
                showTip("导出失败", "error")
                reject(error?.message || error)
            }
        })
    }
}

/**默认的Request实例 */
const request = new myRequest({})
export default request